package com.alone.jpa.query.wrapper;

import cn.hutool.core.collection.CollectionUtil;
import com.alone.jpa.query.condition.Condition;
import com.alone.jpa.query.condition.ConditionType;
import com.alone.jpa.query.condition.PredicateBuilder;
import com.alone.jpa.query.condition.impl.SimpleCondition;
import com.alone.jpa.query.ext.HqlFormatter;
import lombok.SneakyThrows;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;

/**
 * 条件装配器主逻辑实现<p/>
 * 通过建立 方法 -> {@link Condition} 的映射, 委托 {@link PredicateBuilder} 构建出一系列 {@link Predicate} </p>
 *
 * @author Alone
 */
public abstract class AbstractSpecificationWrapper<Type, Col, Children extends AbstractSpecificationWrapper<Type, Col, Children>>
        implements ISpecificationWrapper<Type, Col, Children> {

    private final PredicateBuilder predicateBuilder = new PredicateBuilder();

    @SuppressWarnings("unchecked")
    private final Children typedThis = (Children) this;

    @Override
    public Children eq(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.EQ, column, value);
        return typedThis;
    }

    @Override
    public Children eqCol(boolean condition, Col column1, Col column2) {
        addColCondition(condition, ConditionType.EQ, column1, column2);
        return typedThis;
    }

    @Override
    public Children ne(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.NE, column, value);
        return typedThis;
    }

    @Override
    public Children neCol(boolean condition, Col column1, Col column2) {
        addColCondition(condition, ConditionType.NE, column1, column2);
        return typedThis;
    }

    @Override
    public Children ge(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.GE, column, value);
        return typedThis;
    }

    @Override
    public Children geCol(boolean condition, Col column1, Col column2) {
        addColCondition(condition, ConditionType.GE, column1, column2);
        return typedThis;
    }

    @Override
    public Children le(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.LE, column, value);
        return typedThis;
    }

    @Override
    public Children leCol(boolean condition, Col column1, Col column2) {
        addColCondition(condition, ConditionType.LE, column1, column2);
        return typedThis;
    }

    @Override
    public Children gt(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.GT, column, value);
        return typedThis;
    }


    @Override
    public Children gtCol(boolean condition, Col column1, Col column2) {
        addColCondition(condition, ConditionType.GT, column1, column2);
        return typedThis;
    }

    @Override
    public Children lt(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.LT, column, value);
        return typedThis;
    }

    @Override
    public Children ltCol(boolean condition, Col column1, Col column2) {
        addColCondition(condition, ConditionType.LT, column1, column2);
        return typedThis;
    }

    @Override
    public Children between(boolean condition, Col column, Object value1, Object value2) {
        addCondition(condition, ConditionType.BETWEEN, column, Arrays.asList(value1, value2));
        return typedThis;
    }


    @Override
    public Children notBetween(boolean condition, Col column, Object value1, Object value2) {
        addCondition(condition, ConditionType.NOT_BETWEEN, column, Arrays.asList(value1, value2));
        return typedThis;
    }

    @Override
    public Children like(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.LIKE, column, value);
        return typedThis;
    }


    @Override
    public Children notLike(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.NOT_LIKE, column, value);
        return typedThis;
    }

    @Override
    public Children isNull(boolean condition, Col column) {
        addCondition(condition, ConditionType.IS_NULL, column, null);
        return typedThis;
    }


    @Override
    public Children isNotNull(boolean condition, Col column) {
        addCondition(condition, ConditionType.IS_NOT_NULL, column, null);
        return typedThis;
    }


    @Override
    public Children in(boolean condition, Col column, Collection<?> value) {
        addCondition(condition, ConditionType.IN, column, value);
        return typedThis;
    }

    @Override
    public Children likeRight(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.LIKE_RIGHT, column, value);
        return typedThis;
    }

    @Override
    public Children notLikeRight(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.NOT_LIKE_RIGHT, column, value);
        return typedThis;
    }

    @Override
    public Children likeLeft(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.LIKE_LEFT, column, value);
        return typedThis;
    }

    @Override
    public Children notLikeLeft(boolean condition, Col column, Object value) {
        addCondition(condition, ConditionType.NOT_LIKE_LEFT, column, value);
        return typedThis;
    }

    @Override
    public Children notIn(boolean condition, Col column, Collection<?> value) {
        addCondition(condition, ConditionType.NOT_IN, column, value);
        return typedThis;
    }


    @Override
    public Children apply(boolean condition, String hql) {
        addCondition(condition, ConditionType.HQL, null, hql);
        return typedThis;
    }

    @Override
    public Children apply(boolean condition, HqlFormatter formatter) {
        addCondition(condition, ConditionType.HQL, null, formatter.format());
        return typedThis;
    }

    @Override
    public Children func(boolean condition, Consumer<Children> consumer) {
        if (condition) {
            consumer.accept(typedThis);
        }
        return typedThis;
    }

    @Override
    public Children or(boolean condition) {
        predicateBuilder.addCondition(SimpleCondition.OR_CONDITION);
        return typedThis;
    }

    @Override
    @SneakyThrows
    @SuppressWarnings("unchecked")
    public Children or(boolean condition, Consumer<Children> consumer) {
        Children subWrapper = (Children) this.typedThis.getClass().newInstance();
        consumer.accept(subWrapper);
        addCondition(condition, ConditionType.OR_INNER, null, subWrapper);
        return typedThis;
    }

    @Override
    @SneakyThrows
    @SuppressWarnings("unchecked")
    public Children and(boolean condition, Consumer<Children> consumer) {
        Children subWrapper = (Children) this.typedThis.getClass().newInstance();
        consumer.accept(subWrapper);
        addCondition(condition, ConditionType.AND_INNER, null, subWrapper);
        return typedThis;
    }

    @Override
    public List<Predicate> getPredicates(Root<Type> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        return predicateBuilder.build(root, query, criteriaBuilder);
    }

    @Override
    public Specification<Type> wrap() {
        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = getPredicates(root, query, criteriaBuilder);
            if (CollectionUtil.isNotEmpty(predicates)) {
                query.where(predicates.toArray(new Predicate[0]));
            }
            return null;
        };
    }

    /**
     * 字段转换成字符串
     *
     * @param column 字段
     * @return 字符串
     */
    protected abstract String columnToString(Col column);

    private void addCondition(boolean condition, ConditionType type, Col column, Object value) {
        if (condition) {
            predicateBuilder.addCondition(SimpleCondition.of(type, columnToString(column), value));
        }
    }

    private void addColCondition(boolean condition, ConditionType type, Col column1, Col column2) {
        if (condition) {
            predicateBuilder.addCondition(SimpleCondition.ofColumn(type, columnToString(column1), columnToString(column2)));
        }
    }

}
